DartVM SendPort
介绍
端口(Port)是 Dart 虚拟机底层一种重要通信方式,对于开发者来说,比较熟悉的是 Isolate 间通信,需要通过 ReceivePort 和 SendPort 实现。
SendPort 由 ReceivePort 创建,负责向 ReceivePort 发送信息。每个 SendPort 与一个 ReceivePort 对应。一个 ReceivePort 可以有多个 SendPort。
注意:SendPort 是可以跨 Isolate 传递的,通常用于 Isolate 双向通信机制。
支持类型
都有哪些数据类型支持通过 Port 传递呢?
数据类型包括:Null、bool、int、double、String、List、Map、TransferableTypedData、SendPort、Capability。
SendPort(Dart)
在 Dart 层,SendPort 是一个抽象类,实现比较简单,核心只有一个 send 方法。
send 方法
// 消息会立刻发出,不会阻塞
// 消息会被放到消息队列上,直到传递给接收方
void send(Object? message);
_SendPortImpl(Dart)
_SendPortImpl 是 SendPort 在 Dart 的实现类。核心实现如下:
@pragma("vm:entry-point")
class _SendPortImpl implements SendPort {
// ...
/*--- public interface ---*/
@pragma("vm:entry-point", "call")
void send(var message) {
_sendInternal(message);
}
/*--- private implementation ---*/
_get_id() native "SendPortImpl_get_id";
_get_hashcode() native "SendPortImpl_get_hashcode";
// Forward the implementation of sending messages to the VM.
void _sendInternal(var message) native "SendPortImpl_sendInternal_";
}
可以看到:
- send 方法通过 C/C++ 层的 SendPortImpl_sendInternal_ 实现
- SendPort 还有一个 id 属性,也是通过 C/C++ 层的 SendPortImpl_get_id 获取
SendPort 与 _SendPortImpl 的关联
_SendPortImpl 是怎么与 SendPort 相关联的呢?
答案在 runtime/vm/object.cc 的 Object::Init 方法,这是是在 Isolate 创建的时候进行的一些初始化工作。比如,指定了当 new 一个类的时候,可以由另一个类完成实现:
cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_RawReceivePortImpl(), isolate_lib);
pending_classes.Add(cls);
cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib);
pending_classes.Add(cls);
其中,Symbols::_SendPortImpl 定义在 runtime/vm/symbols.h,是一个字符串:
V(_SendPortImpl, "_SendPortImpl")
这样就能够跟 _SendPortImpl 的 entry-point 注解对应上了。
SendPortImpl_sendInternal_
这个方法非常关键。
DEFINE_NATIVE_ENTRY(SendPortImpl_sendInternal_, 0, 2) {
// 参数解析,第1个是Port,第2个是消息
// SendPort 是C++ 层的
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
// 从 port 中获取端口号
const Dart_Port destination_port_id = port.Id();
const bool can_send_any_object = isolate->origin_id() == port.origin_id();
// 解析 message,Post
// 消息有 3 个参数:端口号、消息内容、优先级
if (ApiObjectConverter::CanConvert(obj.ptr())) {
PortMap::PostMessage(
Message::New(destination_port_id, obj.ptr(), Message::kNormalPriority));
} else {
const bool same_group = FLAG_enable_isolate_groups &&
PortMap::IsReceiverInThisIsolateGroup(
destination_port_id, isolate->group());
if (same_group) {
const auto& copy = Object::Handle(CopyMutableObjectGraph(obj));
auto handle = isolate->group()->api_state()->AllocatePersistentHandle();
handle->set_ptr(copy.ptr());
std::unique_ptr<Message> message(
new Message(destination_port_id, handle, Message::kNormalPriority));
PortMap::PostMessage(std::move(message));
} else {
// TODO(turnidge): Throw an exception when the return value is false?
PortMap::PostMessage(WriteMessage(can_send_any_object, obj,
destination_port_id,
Message::kNormalPriority));
}
}
return Object::null();
}
其中,可以概括为 2 个步骤:
- 解析消息
- PortMap::PostMessage 向消息队列抛消息,这是一个非常重要的方法,将在 PortMap 一文中梳理
代码中的 Dart_Port 是一个整数:
typedef int64_t Dart_Port;
SendPort(C++)
C++ 层的 SendPort 位于 runtime/vm/object.h 中,继承自 C++ 层的 Instance 类。
伴生实例
这个类的实现上有点奇怪,不是一个简单的 C++ 类,感觉是跟 Dart 侧实例共生的 C++ 实例。
目前从感觉上来说:Dart 侧创建一个 SendPort 实例,同时会创建一个伴生的 C++ 实例。
再回到这段初始化 C++ 代码:
cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib);
pending_classes.Add(cls);
这里面包含几重意思:
- New 泛型的第一个,是 C++ 侧的 SendPort
- New 泛型的第二个,是 runtime/vm/compiler/runtime_api.h,这个作用还不太理解,看起来和编译器相关
- _SendPortImpl 关联的是 Dart 侧的实现类
- _SendPortImpl 实现了 SendPort 接口,因此上层业务可以使用 SendPort 接口来使用
进一步总结一下:这是一种将 C++ 类导入到 Dart 世界中的方法。
为什么需要 C++ 实现?
如果 SendPort 只是保留一个整数类型的端口,为什么要大费周折,把一半实现放在 Native,一半实现放在 Flutter?
理由:
- id 参数(端口)只是 SendPort 存储的状态,这个类的状态的确比较简单
- 更加重要的是 SendPort 的 send 方法,这个方法需要来到 C/C++ 层(SendPortImpl_sendInternal_)访问消息队列